package com.fxz.oauth.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fxz.oauth.security.filter.ResAuthenticationFilter;
import com.fxz.oauth.security.filter.JwtFilter;
import com.fxz.oauth.security.userdetails.UserDetailsPasswordServiceImpl;
import com.fxz.oauth.service.impl.UserServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;

import java.util.HashMap;
import java.util.Map;

/**
 * security配置类
 *
 * @author Fxz
 * @version 1.0
 * @date 2021-08-03 13:14
 * 通过排序调整配置类的加载顺序
 */
@Order(99)
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
@Import(SecurityProblemSupport.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserServiceImpl userService;
    private final ObjectMapper objectMapper;
    private final SecurityProblemSupport securityProblemSupport;
    private final UserDetailsPasswordServiceImpl userDetailsPasswordService;
    private final JwtFilter jwtFilter;
    private final PasswordEncoder passwordEncoder;

    /**
     * 配置路径安全
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //请求匹配
                .requestMatchers(req -> req.mvcMatchers("/api/**", "/admin/**", "/authorize/**"))
                //会话管理
                .sessionManagement(sessionManagement -> sessionManagement
                        //会话的策略:无状态
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                //异常处理
                .exceptionHandling(exceptionHandling -> exceptionHandling
                        .authenticationEntryPoint(securityProblemSupport)
                        .accessDeniedHandler(securityProblemSupport))
                //授权请求
                .authorizeRequests(authorizeRequests -> authorizeRequests
                        //放行
                        .antMatchers("/authorize/**").permitAll()
                        //必须具有ADMIN角色
                        .antMatchers("/admin/**").hasRole("ADMIN")
                        //必须具有USER角色
                        .antMatchers("/api/**").hasRole("USER")
                        //所有请求必须验证
                        .anyRequest().authenticated())
                //自定义的过滤器替换用户密码过滤器
                .addFilterAt(resAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                //自定义的jwt过滤器 加在自定义的用户名密码认证过滤器之前
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                //csrf忽略的路径
                .csrf(csrf -> csrf.ignoringAntMatchers("/authorize/**", "/admin/**", "/api/**"))
                .formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable);
    }


    /**
     * 创建一个自定义的用户名密码过滤器
     *
     * @return
     * @throws Exception
     */
    private ResAuthenticationFilter resAuthenticationFilter() throws Exception {
        //创建用户名密码过滤器
        ResAuthenticationFilter filter = new ResAuthenticationFilter(objectMapper);
        //设置认证成功的处理器
        filter.setAuthenticationSuccessHandler(jsonAuthenticationSuccessHandler());
        //设置认证失败的处理器
        filter.setAuthenticationFailureHandler(jsonAuthenticationFailureHandler());
        //设置认证管理器
        filter.setAuthenticationManager(authenticationManager());
        //设置进入过滤器的URL
        filter.setFilterProcessesUrl("/authorize/login");
        return filter;
    }

    /**
     * 登录失败的处理类 （函数接口）
     * 由于登录失败的处理类只有一个未实现的方法 所以我们可以写成一个函数的形式来实现他
     * 函数式编程 返回函数本身
     *
     * @return
     */
    private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
        return (req, res, exp) -> {
            //设置响应状态 401 未授权
            res.setStatus(HttpStatus.UNAUTHORIZED.value());
            //设置内容的类型为application/json
            res.setContentType(MediaType.APPLICATION_JSON_VALUE);
            //设置编码为UTF-8
            res.setCharacterEncoding("UTF-8");
            Map<String, String> map = new HashMap<>();
            map.put("title", "认证失败!");
            map.put("details", exp.getMessage());
            res.getWriter().println(new ObjectMapper().writeValueAsString(map));
        };
    }

    /**
     * 登录成功的处理类 （函数接口）
     * 由于登录成功的处理类只有一个未实现的方法 所以我们可以写成一个函数的形式来实现他
     * 函数式编程 返回函数本身
     *
     * @return
     */
    private AuthenticationSuccessHandler jsonAuthenticationSuccessHandler() {
        return (req, res, auth) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            res.getWriter().println(objectMapper.writeValueAsString(auth));
            res.setStatus(HttpStatus.OK.value());
        };
    }


    /**
     * 不进入过滤器链
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        /*放行错误页*/
        web.ignoring()
                .antMatchers("/error/**", "/h2-console/**");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                //设置DetailsService加载数据源进行验证
                .userDetailsService(userService)
                //设置userDetailsPasswordManager进行密码的编码升级
                .userDetailsPasswordManager(userDetailsPasswordService)
                //设置密码的编码
                .passwordEncoder(passwordEncoder);
    }


}
